#!/opt/php/bin/php -q
<?php
ini_set('display_errors','On');
error_reporting(E_ALL);
define('COREBIND_ROOT',dirname(__FILE__).'/');
$settings = array(
 'listen' 					=> '1.2.3.4',												// DNS-server address
 'listen_port' 			=> 53, 															// Default named port
 'minDNSworkers' 		=> 10,
 'maxDNSworkers' 		=> 20,
 'pidfile' 				=> COREBIND_ROOT.'cutebind.pid', 			// Default pid-file
 'ipcdir' 				=> COREBIND_ROOT.'ipc/', 							// Directory for IPC
 'table'					=> array(															// Simple inline resolving hash-table.
   'some.host.as.example.tld.' => array('A' => '1.2.3.4'),
   'someanotherhost.tld.' => array(
					'A' => '4.5.6.7',
					'NS' => 'ns.example.tld.',
					'SOA' => array(
							'PRIMARY_NS' => 'ns.someanotherhost.tld.',
							'ADMIN_MB' => 'admin.someanotherhost.tld',
							'SERIAL_NUMBER' => 0xFF1F1F1F,
							'REFRESH_INTERVAL' => 3600,
							'RETRY_INTERVAL' => 3600,
							'EXPIRATION' => 3600,
							'MIN_TTL' => 100,
					),
		),
 ),
 'TTL'						=> 58,																// Time-To-Life (TTL)
 'configfile'			=> COREBIND_ROOT.'config.php',				// Config-file
 'cutebind_path'	=> 'cutebind',												// Path to CuteBind's executable file.
 'logging'				=> 1,																	// Logging level
 'accesslogstorage'=> COREBIND_ROOT.'logs/%DATE=d.m.Y%.log', // Log storage. This field has special syntax, but it allows simple path.
 'errorlogstorage'=> COREBIND_ROOT.'logs/error.log',		// Log storage. This field has special syntax, but it allows simple path.
 'intfile'				=> '/tmp/cutebind.intfile.tmp',				// This path must be a point to file in writable folder (for all CuteBind processes).
 'setuser'				=> '',																// You can set user of master process (sudo).
 'setgroup'				=> '',																// You can set group of master process (sudo).
 'DNS' => array(
  'TC'=> '0', // Truncate to 512 bytes
  'RA'=> '1', // Recursive queries enabled? (1/0)
 ),
);

$table = &$settings['table'];
$ver = '0.1b';
if (!function_exists('setproctitle')) {function setproctitle($t) {}}
require_once COREBIND_ROOT.'core/core-bytes.php';
require_once COREBIND_ROOT.'core/core-cli.php';
require_once COREBIND_ROOT.'core/core-date.php';
function roundrobin($addrs,$client)
{
 if (is_string($client)) {$client = ip2long($client);}
 srand($client);
 $k = rand(0,sizeof($addrs)-1);
 $r = $addrs[$k];
 srand(microtime(TRUE)+microtime(TRUE)+microtime(TRUE));
 return $r;
}
function log_access($log,$reopen = FALSE)
{
 global $settings;
 static $p;
 $log .= "\n";
 if (($p === NULL) || $reopen) {$p = fopen(parse_storagepath($settings['accesslogstorage']),'a');}
 if ($log !== "\n")
 {
  fwrite($p,$log);
  echo $log;
 }
}
function log_error($log,$reopen = FALSE)
{
 global $settings;
 static $p;
 $log .= "\n";
 if (($p === NULL) || $reopen) {$p = fopen(parse_storagepath($settings['errorlogstorage']),'a');}
 if ($log !== "\n")
 {
  fwrite($p,$log);
  echo $log;
 }
}
function parse_storagepath_callback1($m)
{
 $e = explode('=',$m[1]);
 if ($e[0] == 'DATE') {return date($e[1]);}
 return $m[0];
}
function parse_storagepath($path)
{
 $path = preg_replace_callback('~%(.*?)%~','parse_storagepath_callback1',$path);
 if (stripos($path,'file://') === 0) {$path = substr($path,7);}
 return $path;
}

$runmode = isset($_SERVER['argv'][1])?str_replace('-','',$_SERVER['argv'][1]):'';
$_SERVER['argv'][1] = '';
$args = getArgs($_SERVER['argv']);
$args_ex = '';

if (isset($args['configfile'])) {$settings['configfile'] = $args['configfile'];}
if ($settings['configfile'] !== '') {include $settings['configfile'];}
if (!function_exists('resolver'))
{
 function resolver(&$cq) {}
}
else
{
 resolver($null,TRUE);
}
foreach ($args as $k => $v)
{
 $ok = TRUE;
 if ($k == 'pidfile') {$settings[$k] = $v;}
 elseif ($k == 'listen') {$settings['listen'] = $v;}
 elseif ($k == 'listenport') {$settings['listen_port'] = $v;}
 elseif ($k == 'cutebindpath') {$settings[$k] = $v;}
 elseif ($k == 'configfile') {$settings[$k] = $v;}
 elseif ($k == 'logging') {$settings[$k] = (int) $v;}
 elseif ($k == 'logstorage') {$settings[$k] = $v;}
 elseif ($k == 'setuser') {$settings[$k] = $v;}
 elseif ($k == 'setgroup') {$settings[$k] = $v;}
 elseif ($k == 'intfile') {$settings[$k] = $v;}
 else {fwrite(STDERR,'[Warning] Unknown parameter \''.$k."'\n"); $ok = FALSE;}
 if ($ok) {$args_ex .= ($args_ex !== ''?' ':'').'--'.$k.'=\''.$v.'\'';}
}
$pidfile = realpath($settings['pidfile']);
if (!file_exists($pidfile)) {touch($pidfile);}
if ($runmode != 'master') // We are control script
{
 $_pid = (float) file_get_contents($pidfile);
 $startcmd = ($cmd = $settings['cutebind_path'].' master '.rtrim($args_ex)).' >> cutebind.out &';
 if ($runmode == 'status' or $runmode == 'fullstatus')
 {
  echo '[STATUS] CuteBind '.$ver.' is '.(($_pid and posix_kill($_pid,SIGHUP))?'running':'NOT running').' ('.$pidfile.").\n";
  if ($runmode == 'fullstatus')
  {
   echo 'Fetching status... ';
   if (file_exists($settings['intfile'])) {unlink($settings['intfile']);}
   if ($_pid and posix_kill($_pid,SIGUSR1))
   {
    $ok = TRUE;
    $i = 0;
    while (!file_exists($settings['intfile']))
    {
     $ok = FALSE;
     usleep(500000);
     if ($i > 30) {break;}
     ++$i;
    }
    if ($ok)
    {
     echo "OK.\n".file_get_contents($settings['intfile'])."\n\n";
     unlink($settings['intfile']);
    }
    else {echo "FAILED.\n\n";}
   }
  }
 }
 elseif ($runmode == 'start')
 {
  if ($_pid && posix_kill($_pid,SIGHUP)) {echo 'ERROR. CuteBind with pid-file \''.$pidfile.'\' is running alredy (PID '.$_pid.")\n";}
  else
  {
   shell_exec($startcmd);
   $i = 0;
   $ok = TRUE;
   while (TRUE)
   {
    if (file_exists($pidfile) && ($_pid = file_get_contents($pidfile)) && posix_kill($_pid,SIGHUP))
    {
     $ok = TRUE;
     break;
    }
    $ok = FALSE;
    usleep(500000);
    if ($i > 10) {break;}
    ++$i;
   }
   if (!$ok) {echo "[START] Starting... ERROR. Process hasn't daemonized (PID - ".($_pid?$_pid:'UNKNOWN')."). Try to run the following command manually and fix an error:\n".$cmd."\n";}
  }
 }
 elseif (($runmode == 'restart') || ($runmode == 'graceful'))
 {
  echo '[RESTART] Sending SIGTERM to '.$_pid.'... '.(($ok = ($_pid && posix_kill($_pid,SIGTERM)))?'OK.':"ERROR. It seems that CuteBind is not running.\n");
  if ($ok)
  {
   $i = 0;
   while ($r = $running = posix_kill($_pid,SIGHUP))
   {
    usleep(500000);
    if ($i == 9)
    {
     echo ' CuteBind master-process hasn\'t finish. Sending SIGKILL... '.(($ok = ($_pid && posix_kill($_pid,SIGKILL)))?'OK.':'ERROR. Permissions?')."\n";
     if ($ok)
     {
      if (!posix_kill($_pid,SIGHUP)) {$running = FALSE; echo " Oh, his blood is on my hands :'(\n";}
      else {$running = TRUE; echo "ERROR: Process alive. Permissions?\n";}
     }
     break;
    }
    ++$i;
   }
   if (!$r) {echo "\n";}
  }
  else {$running = FALSE;}
  if (!$running)
  {
   echo "[START] Starting CuteBind... ";
   shell_exec($startcmd);
   $i = 0;
   $ok = FALSE;
   while (TRUE)
   {
    if (file_exists($pidfile) && ($_pid = file_get_contents($pidfile)) && posix_kill($_pid,SIGHUP))
    {
     $ok = TRUE;
     break;
    }
    $ok = FALSE;
    usleep(500000);
    if ($i > 10) {break;}
    ++$i;
   }
   if ($ok) {echo "OK.\n";}
   else {echo "ERROR. Process hasn't daemonized. Try to run the following command manually and fix an error:\n".$cmd."\n";}
  }
 }
 elseif ($runmode == 'stop')
 {
  echo '[STOP] Sending SIGTERM to '.$_pid.'... '.(($ok = ($_pid && posix_kill($_pid,SIGTERM)))?'OK.':'ERROR. It seems that CuteBind is not running.');
  if ($ok)
  {
   $i = 0;
   while ($r = posix_kill($_pid,SIGHUP))
   {
    usleep(500000);
    if ($i == 9)
    {
     echo ' CuteBind master-process hasn\'t finish. Sending SIGKILL... '.(($ok = posix_kill($_pid,SIGKILL))?'OK.':'ERROR. Permissions?')."\n";
     if ($ok)
     {
      if (!posix_kill($_pid,SIGHUP)) {$running = FALSE; echo " Oh, his blood is on my hands :'(\n";}
      else {$running = TRUE; echo "ERROR: Process alive. Permissions?\n";}
     }
     break;
    }
    ++$i;
   }
   if (!$r) {echo "\n";}
  }
  else {echo "\n";}
 }
 elseif ($runmode == 'help')
 {
  echo 'CuteBind '.$ver.". Made by gf@hackweb.org.\n
\tusage: cutebind (start|stop|restart|fullstatus|status|graceful|configtest|help) ...
\tAlso you can use some optional parameters to override the same config variables.
\t--pid-file='/path/to/pid-file'  -  (Pid-file)
\t--listen='1.2.3.4'  -  (Hosts of the DNS-server, no default) - Comma separated
\t--listen-port=53  -  (Default port of the DNS-server, default: 53)
\t--cutebind-path='cutebind'  -  (You can set a path to CuteBind's sh-file)
\t--config-file='/path/to/config.php'  -  (You can set a path to configuration file manually)
\t--logging=1  -  (Logging. 1-Enable, 0-Disable)
\t--log-storage='/path/to/logs/log%DATE=d.m.Y%.log'  -  (Log storage. This field has special syntax, but it allows simple path.)
\t--set-user=cutebind  -  (You can set user of master process (aka sudo).) 
\t--set-group=cutebind  -  (You can set user of master process (aka sudo).)
\t--int-file=  -  (This path must be a point to file in writable folder for all CuteBind processes).
\t--manual  -  (Open the manual pages.)
\t--help  -  (This help information.)
  \n\n";
 }
 else {echo "usage: cutebind (start|stop|restart|fullstatus|status|graceful|configtest|help) ...\n"; exit(0);}
 exit(0);
}
// Now, we are the master or the worker (pre-worker). Let's init environment...
setproctitle('cutebind: master process');
$dns_sockets = array();
$zmap = array();
$ipc = array();
$ipc_keys = array();
define('MASTER_PID',getmypid());
 
if (version_compare(PHP_VERSION,'5.3.0','<')) {declare(ticks = 1);}


$QTYPES = array(
 1 => 'A', 2 => 'NS',  3 => 'MD', 4 => 'MF', 5 => 'CNAME',
 6 => 'SOA', 7 => 'MB', 8 => 'MG', 9 => 'MR', 10 => 'RR',
 11 => 'WKS', 12 => 'PTR', 13 => 'HINFO', 14 => 'MINFO',
 15 => 'MX', 16 => 'TXT', 17 => 'RP', 18 => 'AFSDB',
 19 => 'X25', 20 => 'ISDN',
 21 => 'RT', 22 => 'NSAP', 23 => 'NSAP-PTR', 24 => 'SIG',
 25 => 'KEY', 26 => 'PX', 27 => 'GPOS', 28 => 'AAAA', 29 => 'LOC',
 30 => 'NXT', 31 => 'EID', 32 => 'NIMLOC', 33 => 'SRV',
 34 => 'ATMA', 35 => 'NAPTR', 36 => 'KX', 37 => 'CERT', 38 => 'A6',
 39 => 'DNAME', 40 => 'SINK', 41 => 'OPT', 42 => 'APL', 43 => 'DS',
 44 => 'SSHFP', 45 => 'IPSECKEY', 46 => 'RRSIG', 47 => 'NSEC',
 48 => 'DNSKEY', 49 => 'DHCID', 50 => 'NSEC3', 51 => 'NSEC3PARAM',
 55 => 'HIP', 99 => 'SPF', 100 => 'UINFO', 101 => 'UID', 102 => 'GID',
 103 => 'UNSPEC', 249 => 'TKEY', 250 => 'TSIG', 251 => 'IXFR',
 252 => 'AXFR', 253 => 'MAILB', 254 => 'MAILA', 255 => 'ALL',
 32768 => 'TA', 32769 => 'DLV',
);
$QCLASSES = array(
 1 => 'IN',
 3 => 'CH',
 255 => 'ANY',
);
$ipc_files = array();
$ipc_prefix = dechex(crc32($settings['pidfile']));

touch($tp = $settings['ipcdir'].$ipc_prefix.'.dns-status-map.cbt');
$ipc_files[] = $tp;
$ipc_keys['dns-status-map'] = ftok($tp,'t');
if (defined('IS_64b')) {$ipc_keys['dns-status-map'] = $ipc_keys['dns-status-map']+($ipc_keys['dns-status-map']/pow(10,strlen($ipc_keys['dns-status-map'])));}
$ipc['dns-status-map'] = shmop_open($ipc_keys['dns-status-map'],'c',0644,1024);
if (!$ipc['dns-status-map']) 
{
 log_error('[ERROR] Couldn\'t create shared memory segment \'dns-status-map\'');
 exit(0);
}

touch($tp = $settings['ipcdir'].$ipc_prefix.'.dns-q-counter.cbt');
$ipc_files[] = $tp;
$ipc_keys['dns-q-counter'] = ftok($tp,'t');

if (defined('IS_64b')) {$ipc_keys['dns-status-map'] = $ipc_keys['dns-status-map']+($ipc_keys['dns-status-map']/pow(10,strlen($ipc_keys['dns-status-map'])));}
$ipc['dns-q-counter'] = shmop_open($ipc_keys['dns-q-counter'],'c',0644,1024*4);
if (!$ipc['dns-q-counter']) 
{
 log_error('[ERROR] Couldn\'t create shared memory segment \'dns-q-counter\'');
 exit(0);
}
shmop_write($ipc['dns-q-counter'],str_repeat("\x00",1024*4),0);

function worker_sighandler($signo)
{
 static $signals = array(
  SIGTERM => 'SIGTERM',
  SIGUSR1 => 'SIGUSR1 (Fullstatus)',
  SIGUSR2 => 'SIGUSR2 (Re-open logs)',
  SIGHUP => 'SIGHUP (Ping-pong)'
 );
 echo 'Caught signal '.$signals[$signo].".\n";
 global $settings;
 global $q_counter;
 if ($signo == SIGTERM) {define('TERMINATED',TRUE);}
 elseif ($signo == SIGHUP) {}
 elseif ($signo == SIGUSR1) // Caught fullstatus-report
 {
 }
 elseif ($signo == SIGUSR2) // Caught reopen logs
 {
  log_access('',TRUE);
  log_error('',TRUE);
 }
 elseif ($signo == SIGHUP) // Caught status-check
 {
 }
}
function run_worker($wtype = '')
{
 global $settings;
 global $zmap;
 global $dns_sockets;
 global $ipc;
 global $ipc_keys;
 global $table;
 global $QTYPES;
 global $QCLASSES;
 $q_counter = 0;
 if (!isset($zmap[$wtype])) {$zmap[$wtype] = array();}
 $zinfo = array(-1,time(),sizeof($zmap[$wtype]));
 $pid = pcntl_fork();
 if ($pid == -1) {die('could not fork');}
 elseif ($pid) // we are the master
 {
  $zinfo[0] = $pid;
  $zmap[$wtype][] = $zinfo;
  return;
 }
 $zinfo[0] = getmypid();
 // we are the worker
 setproctitle('cutebind: worker process'); 
 pcntl_signal(SIGTERM,'worker_sighandler');
 pcntl_signal(SIGHUP,'worker_sighandler');
 pcntl_signal(SIGUSR1,'worker_sighandler');
 pcntl_signal(SIGUSR2,'worker_sighandler');

 while (TRUE)
 {
  if (defined('TERMINATED'))
  {
   exit(0);
  }
  shmop_write($ipc['dns-status-map'],"\x00",$zinfo[2]);
  $read = $dns_sockets;
  $num = @stream_select($read,$write = NULL,$except = NULL,0,200000);
  if (FALSE === $num)
  {
   // Error.
   exit;
   continue;
  }
  elseif ($num === 0)
  {
   continue;
  }
  $data = stream_socket_recvfrom($bind = $read[0],1024,0,$peer);
  if (($data === '') || ($data === FALSE)) {continue;}
  shmop_write($ipc['dns-status-map'],"\x01",$zinfo[2]);
  //var_dump(array(hecho($data)));
  ++$q_counter;
  shmop_write($ipc['dns-q-counter'],_dword($q_counter),$zinfo[2]*4);
 
  $qinfo = array();
  $qinfo['id'] = _get_word($data);
  $bitmap = getbitmap(_get_byte($data));
  $qinfo['QR'] = $bitmap[0];
  $qinfo['OPCODE'] = bindec(substr(1,4));
  $qinfo['AA'] = $bitmap[5];
  $qinfo['TC'] = $bitmap[6];
  $qinfo['RD'] = $bitmap[7];
  $bitmap = getbitmap(_get_byte($data));
  $qinfo['RA'] = $bitmap[0];
  $qinfo['Z'] = substr($bitmap,1,3);
  $qinfo['RCODE'] = bindec(substr($bitmap,4,4));
  $qinfo['QDCOUNT'] = _get_word($data);
  $qinfo['ANCOUNT'] = _get_word($data);
  $qinfo['NSCOUNT'] = _get_word($data);
  $qinfo['ARCOUNT'] = _get_word($data);
  $answer = '';
  $qinfo['REPLYCODE'] = 0;
  $qinfo['AA'] = 1;
  //var_dump($qinfo);
  if ($qinfo['QR'] == '0') // Query
  {
   for ($qn = 0; $qn < $qinfo['QDCOUNT']; ++$qn)
   {
    $cq = array('domain' => '','bin_domain' => '');
    $e = explode(':',$peer);
    $cq['peer_ip'] = $e[0];
    $cq['peer_port'] = $e[1];
    for (;;)
    {
     $l = ord($data[0]);
     $p = substr($data,1,$l);
     $cq['domain'] .= $p.(($l !== 0)?'.':'');
     $cq['bin_domain'] .= $data[0].$p;
     $data = substr($data,$l+1);
     if ($l === 0) {break;}
    }
    $cq['IP'] = '';
    $cq['RDATA'] = '';
    $cq['l_domain'] = strtolower($cq['domain']);
    $cq['QTYPE_INT'] = _get_word($data);
    $cq['QCLASS_INT'] = _get_word($data);
    $cq['QTYPE'] = isset($QTYPES[$cq['QTYPE_INT']])?$QTYPES[$cq['QTYPE_INT']]:'UNK '.$cq['QTYPE_INT'];
    $cq['QCLASS'] = isset($QCLASSES[$cq['QCLASS_INT']])?$QCLASSES[$cq['QCLASS_INT']]:'UNK '.$cq['QCLASS_INT'];
    $cq['TTL'] = $settings['TTL'];
    if (($cq['QTYPE'] == 'A') || ($cq['QTYPE'] == 'PTR'))
    {
     if ($cq['QTYPE'] == 'PTR') {$cq['QTYPE_INT'] = 0x01;}
     if (($cq['QTYPE'] == 'PTR') || ($qinfo['OPCODE'] == '1') || (substr($cq['l_domain'],-14) == '.in-addr.arpa.'))
     {
      $e = explode('.',$cq['domain']);
      $cq['IP'] = $e[3].'.'.$e[2].'.'.$e[1].'.'.$e[0];
      $cq['REVERSE'] = TRUE;
      if (isset($table[$cq['IP']]['PTR'])) {$cq['R_DOMAIN'] = $table[$cq['IP']]['PTR'];}
      elseif (resolver($cq)) {}
      else {$cq['R_DOMAIN'] = gethostbyaddr($cq['IP']);}
      if (substr($cq['R_DOMAIN'],-1) != '.') {$cq['R_DOMAIN'] .= '.';}
      $cq['bin_domain'] = _labels($cq['R_DOMAIN']);
      //if (substr($cq['bin_domain'],-1) != "\x00") {$cq['bin_domain'] .= "\x00";}
     }
     elseif (isset($table[$cq['l_domain']][$cq['QTYPE']])) {$cq['IP'] = $table[$cq['l_domain']][$cq['QTYPE']];}
     elseif (resolver($cq)) {}
     else
     {
      $cq['IP'] = gethostbyname($cq['domain']);
      if ($cq['IP'] == $cq['domain']) {$cq['IP'] = '';}
     }
     if ((!isset($cq['REVERSE'])) || !$cq['REVERSE'])
     {
      if ((strpos($cq['IP'],'RR(') === 0) && (substr($cq['IP'],-1) == ')'))
      {
       $cq['IP'] = roundrobin(explode(',',substr($cq['IP'],3,-1)),$cq['peer_ip']);
      }
     }
     if ($cq['IP'] == '') {$qinfo['REPLYCODE'] = 3;}
     if ($qinfo['REPLYCODE'] == 0)
     {
      if (isset($cq['REVERSE']) && $cq['REVERSE']) {$cq['RDATA'] = $cq['R_DOMAIN'];}
      else {$cq['RDATA'] = _dword(ip2long($cq['IP'])); $cq['dest'] = $cq['IP'];}
     }
    }
    elseif ($cq['QTYPE'] == 'NS')
    {
     if (isset($table[$cq['l_domain']]['NS'])) {$cq['NS'] = $table[$cq['l_domain']]['NS'];}
     elseif (resolver($cq)) {}
     else {$qinfo['REPLYCODE'] = 4;}
     if (($qinfo['REPLYCODE'] == 0) && isset($cq['NS']))
     {
      $cq['RDATA'] .= _labels($cq['NS']);
      $cq['dest'] = 'NS ('.$cq['NS'].')';
     }
    }
    elseif ($cq['QTYPE'] == 'MX')
    {
     if (isset($table[$cq['l_domain']]['MX'])) {$cq['MX'] = $table[$cq['l_domain']]['MX'];}
     elseif (resolver($cq)) {}
     else {$qinfo['REPLYCODE'] = 4;}
     if (($qinfo['REPLYCODE'] == 0) && isset($cq['MX']))
     {
      $e = explode('=',$cq['MX']);
      $cq['RDATA'] = _word($e[0]);
      $cq['RDATA'] .= _labels($e[1]);
      $cq['dest'] = 'MX ('.$e[0].'='.$e[1].')';
     }
    }
    elseif ($cq['QTYPE'] == 'SOA')
    {
     if (isset($table[$cq['l_domain']]['SOA'])) {$cq['SOA'] = $table[$cq['l_domain']]['SOA'];}
     elseif (resolver($cq)) {}
     else {$qinfo['REPLYCODE'] = 4;}
     if (($qinfo['REPLYCODE'] == 0) && isset($cq['SOA']))
     {
      $cq['RDATA'] = _labels($cq['SOA']['PRIMARY_NS']);
      $cq['RDATA'] .= _labels($cq['SOA']['ADMIN_MB']);
      $cq['RDATA'] .= _dword($cq['SOA']['SERIAL_NUMBER']);
      $cq['RDATA'] .= _dword($cq['SOA']['REFRESH_INTERVAL']);
      $cq['RDATA'] .= _dword($cq['SOA']['RETRY_INTERVAL']);
      $cq['RDATA'] .= _dword($cq['SOA']['EXPIRATION']);
      $cq['RDATA'] .= _dword($cq['SOA']['MIN_TTL']);
      $cq['dest'] = 'SOA ('.implode(';',$cq['SOA']).')';
     }
    }
    else {$qinfo['REPLYCODE'] = 4;}
    if ($settings['logging'])
    {
     if (isset($cq['REVERSE']) && $cq['REVERSE']) {$lp = $cq['IP'].' -> '.$cq['R_DOMAIN'];}
     else {$lp = '\''.$cq['domain'].'\' -> '.$cq['dest'];}
     log_access('['.date('r').'] ['.$peer.'] (TYPE='.$cq['QTYPE'].',CLASS='.$cq['QCLASS'].',OPCODE='.$qinfo['OPCODE'].',RCODE='.$qinfo['REPLYCODE'].') '.$lp);
    }
    if (($qinfo['REPLYCODE'] == 0) || ($qinfo['REPLYCODE'] == 4))
    {
     $answer .= $cq['bin_domain'];
     $answer .= _word($cq['QTYPE_INT']);
     $answer .= _word($cq['QCLASS_INT']);
     $answer .= "\xc0\x0c"; //p1
     $answer .= _word($cq['QTYPE_INT']); //p1-1
     $answer .= _word($cq['QCLASS_INT']); //p1-2
     $answer .= _dword($cq['TTL']);
     $answer .= _word(strlen($cq['RDATA'])); // RDLENGTH
     $answer .= $cq['RDATA'];
    }
   }
  }
  $g = TRUE;
  $QDCOUNT = $qinfo['QDCOUNT']; // Count of queries in packet
  $ANCOUNT = $qinfo['QDCOUNT']; // Count of records included in query
  $NSCOUNT = 0; // Count of source records of authority servers
  $ARCOUNT = 0; // Count of records in additional information field
  $packet = '';
  $packet .= _word($qinfo['id']); // ID

  $bitmap = '';
  $bitmap .= '1'; // Query Type (0 - Query, 1 - Response)
  $bitmap .= sprintf('%04b',$qinfo['OPCODE']); // OP-code (0 - Standart, 1 - Reverse, 2 - Server status query) (4 bits)
  $bitmap .= $qinfo['AA']?'1':'0'; // Authority (1/0)
  $bitmap .= $settings['DNS']['TC']?'1':'0'; // Truncate to 512 bytes
  $bitmap .= $qinfo['RD']?'1':'0'; // Recursive flag
  $bitmap .= $settings['DNS']['RA']?'1':'0'; // Recursive queries enabled? (1/0)
  $bitmap .= '000'; // Zero (3 bits)
  $bitmap .= sprintf('%04b',$qinfo['REPLYCODE']);// Response code (0 - No error, 1 - Query packet error, 2 - Internal server error, 3 - Not found, 4 - Query type is not supported, 5 - Query rejected by server) (4 bits)
  $packet .= bitmap2bytes($bitmap,2);
  
  $packet .= _word($QDCOUNT); // Count of queries in packet
  $packet .= _word($ANCOUNT); // Count of records included in query
  $packet .= _word($NSCOUNT); // Count of source records of authority servers
  $packet .= _word($ARCOUNT); // Count of records in additional information field
  
  //var_dump(array('not-work',hecho($packet),hecho($answer)));
  $packet .= $answer;
  stream_socket_sendto($bind,$packet,0,$peer);
 }
}
if ($runmode == 'master') // We are the master
{
 $e = explode(',',$settings['listen']);
 for ($i = 0, $s = sizeof($e); $i < $s; ++$i)
 {
  $ih = explode(':',$e[$i]);
  if (!isset($ih[1])) {$ih[1] = $settings['listen_port'];}
  ($bind = stream_socket_server('udp://'.$ih[0].':'.$ih[1],$errno,$errstr,STREAM_SERVER_BIND)) || exit(0);
  //if (!socket_set_option($bind,SOL_SOCKET,SO_REUSEADDR,1)) {exit(socket_strerror(socket_last_error($bind)));}
  $dns_sockets[] = $bind;
 }
 if (isset($settings['setgroup']) && ($settings['setgroup'] !== ''))
 {
  if (($sg = posix_getgrnam($settings['setgroup'])) === FALSE)
  {
   log_access('[ERROR] Couldn\'t change group to \''.$settings['setgroup'].'\'. You must replace config-variable \'setuser\' with existing username.');
   exit(0);
  }
  elseif (($sg != getmygid()) && (!posix_setgid($sg['gid'])))
  {
   log_access('[ERROR] Couldn\'t change group to \''.$settings['setgroup']."'. Error (".posix_get_last_error().'): '.posix_strerror(posix_get_last_error()));
   exit(0);
  }
 }
 if (isset($settings['setuser']) && ($settings['setuser'] !== ''))
 {
  if (($su = posix_getpwnam($settings['setuser'])) === FALSE)
  {
   log_error('[ERROR] Couldn\'t change user to \''.$settings['setuser'].'\', user not found. You must replace config-variable \'setuser\' with existing username.');
   exit(0);
  }
  elseif (($su != getmyuid()) && (!posix_setuid($su['uid'])))
  {
   log_error('[ERROR] Couldn\'t change user to \''.$settings['setuser']."'. Error (".posix_get_last_error().'): '.posix_strerror(posix_get_last_error()));
   exit(0);
  }
 }
 if (!sizeof($dns_sockets))
 {
  $msg = "[ERROR] No listening ports for DNS-server.\n";
  fwrite($plog,$msg);
  echo $msg;
  exit(0);
 }
 for ($i = 0; $i < $settings['minDNSworkers']; ++$i)
 {
  run_worker('dns');
 }
 echo '[STATUS] CuteBind. System is up. Accepting connections on '.$settings['listen'].".\n\n";
 
 define('START_TIME',time());

 function get_concurency($t)
 {
  global $ipc;
  global $zmap;
  $tmp = shmop_read($ipc[$t.'-status-map'],0,1024);
  $c = 0;
  foreach ($zmap[$t] as $v)
  {
   if (substr($tmp,$v[2],1) == "\x01") {++$c;}
  }
  return $c;
 }
 function get_q_counter($t)
 {
  global $ipc;
  global $zmap;
  $tmp = shmop_read($ipc[$t.'-q-counter'],0,1024*4);
  $c = 0;
  foreach ($zmap[$t] as $v)
  {
   $c += _get_dword(substr($tmp,$v[2]*4,4));
  }
  return $c;
 }
 function reopen_logstorages()
 {
  global $zmap;
  log_access('',TRUE);
  log_error('',TRUE);
  foreach ($zmap['dns'] as $v) {posix_kill($v[0],$signo);}
 }
 function master_sighandler($signo)
 {
  static $signals = array(
   SIGTERM => 'SIGTERM',
   SIGUSR1 => 'SIGUSR1 (Fullstatus)',
   SIGHUP => 'SIGHUP (Ping-pong)'
  );
  echo 'Caught signal '.$signals[$signo].".\n";
  global $settings;
  global $zmap;
  global $ipc_files;
  if ($signo == SIGTERM)
  {
   foreach ($zmap['dns'] as $v) {posix_kill($v[0],SIGTERM);}
   foreach ($ipc_files as $v) {unlink($v);}
   define('TERMINATED',TRUE);
   if (defined('TERMINATED')) {exit(0);}
  }
  elseif ($signo == SIGHUP) {}
  elseif ($signo == SIGUSR1) // Caught fullstatus-report
  {
   $fp = fopen($settings['intfile'].'.new','w');
   $v = time()-START_TIME;
   $q_counter = get_q_counter('dns');
   $status = 'Uptime: '.date_period_text(START_TIME,time())."\n";
   $status .= 'Concurency DNS-queries: '.get_concurency('dns')."\n";
   $status .= 'Total queries: '.$q_counter."\n";
   $status .= 'Queries per second (avg): '.($q_counter/$v)."\n";
   $status .= 'real_mem='.memory_get_usage(TRUE)."\n";
   $status .= 'emalloc_mem='.memory_get_usage(FALSE)."\n";
   $i = 0;
   foreach (getrusage() as $k => $v) {$status .= $k.'='.$v.((++$i%2 == 0)?"\n":"\t\t\t\t");}
   fwrite($fp,$status);
   fclose($fp);
   rename($settings['intfile'].'.new',$settings['intfile']);
  }
  elseif ($signo == SIGUSR2) // Caught reopen logs
  {
   reopen_logstorages();
  }
  elseif ($signo == SIGHUP) // Caught status-check
  {
  }
 }
 pcntl_signal(SIGTERM,'master_sighandler');
 pcntl_signal(SIGHUP,'master_sighandler');
 pcntl_signal(SIGUSR1,'master_sighandler');
 pcntl_signal(SIGUSR2,'master_sighandler');

 $fp = fopen($pidfile,'w');
 fwrite($fp,getmypid());
 fclose($fp);


 function check_logstorages($interval)
 {
  global $settings;
  static $l;
  $a = array(
   'access' => parse_storagepath($settings['accesslogstorage']),
   'error' => parse_storagepath($settings['errorlogstorage'])
  );
  $k = array_keys($a);
  if ($l === NULL) {$l = $a; return;}
  for ($i = 0,$s = sizeof($l); $i < $s; ++$i)
  {
  if ($a[$k[$i]] != $l[$k[$i]])
   {
    reopen_logstorages();
    break;
   }
  }
 }
 function check_loadbalance($interval)
 {
  global $settings;
  global $zmap;
  static $counter = 0;
  if ($counter < 0) {$counter = 0;}
  if (get_concurency('dns') == sizeof($zmap['dns'])) {++$counter;}
  else {--$counter;}
  if ($counter >= (20/$interval))
  {
   if (sizeof($zmap['dns'])+1 >= $settings['maxDNSworkers']) {log_error('[LOAD-BALANCER] It looks like you should increase \'maxDNSworkers\' variable.');}
   else {run_worker('dns');}
   --$counter;
  }
 }
 $schedule = array(
  'check_logstorages' => 10,
  'check_loadbalance' => 5,
 );
 $schedule_last = array();
 while (TRUE)
 {
  sleep(5);
  for ($i = 0, $k = array_keys($schedule), $s = sizeof($schedule); $i < $s; ++$i)
  {
   if (!isset($schedule_last[$k[$i]])) {$schedule_last[$k[$i]] = 0;}
   if ($schedule_last[$k[$i]] < time()-$schedule[$k[$i]])
   {
    $schedule_last[$k[$i]] = time();
    call_user_func($k[$i],$schedule[$k[$i]]);
   }
  }
 }
}
